
/*
A shoot-em-up game.
Uses CHR RAM to draw attacker tiles at 8 different pixels,
then animates them in the nametable.
Sprites are used for the player, missiles, attackers, and stars.
*/

#include <stdlib.h>
#include <string.h>
#include <nes.h>

#include "./../neslib/neslib.h"

// APU (sound) support
#include "apu.h"

// BCD arithmetic support
#include "bcd.h"

// VRAM update buffer
#include "vrambuf.h"

#define COLS 32
#define ROWS 28

//#define DEBUG_FRAMERATE

/*{pal:"nes",layout:"nes"}*/
const char PALETTE[32] = {
    0x0F,

    0x11, 0x24, 0x3C, 0x00,
    0x01, 0x15, 0x25, 0x00,
    0x01, 0x10, 0x20, 0x00,
    0x06, 0x16, 0x26, 0x00,

    0x11, 0x24, 0x3C, 0x00,
    0x01, 0x15, 0x25, 0x00,
    0x31, 0x35, 0x3C, 0x00,
    0x25, 0x1C, 0x3D};

#define COLOR_PLAYER 3
#define COLOR_FORMATION 1
#define COLOR_ATTACKER 1
#define COLOR_MISSILE 3
#define COLOR_BOMB 2
#define COLOR_SCORE 2
#define COLOR_EXPLOSION 3

/*{w:8,h:8,bpp:1,count:128,brev:1,np:2,pofs:8,remap:[0,1,2,4,5,6,7,8,9,10,11,12]}*/
const char TILESET[128 * 8 * 2] = {
    // font (0..63)
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x38,
    0x7C,
    0x7C,
    0x7C,
    0x38,
    0x00,
    0x38,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x6C,
    0x6C,
    0x48,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x6C,
    0xFE,
    0x6C,
    0xFE,
    0x6C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x10,
    0xFE,
    0xD0,
    0xFE,
    0x16,
    0xFE,
    0x10,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xCE,
    0xDC,
    0x38,
    0x76,
    0xE6,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x38,
    0x6C,
    0x7C,
    0xEC,
    0xEE,
    0x7E,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x38,
    0x38,
    0x30,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x38,
    0x70,
    0x70,
    0x70,
    0x70,
    0x70,
    0x38,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x70,
    0x38,
    0x38,
    0x38,
    0x38,
    0x38,
    0x30,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x6C,
    0x38,
    0x6C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x38,
    0x38,
    0xFE,
    0x38,
    0x38,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x30,
    0x30,
    0x60,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x60,
    0x60,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x0E,
    0x1E,
    0x3C,
    0x78,
    0xF0,
    0xE0,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xEE,
    0xEE,
    0xEE,
    0xEE,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x38,
    0x78,
    0x38,
    0x38,
    0x38,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0x0E,
    0x7C,
    0xE0,
    0xEE,
    0xFE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFC,
    0x0E,
    0x3C,
    0x0E,
    0x0E,
    0xFC,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x3E,
    0x7E,
    0xEE,
    0xEE,
    0xFE,
    0x0E,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFC,
    0xE0,
    0xFC,
    0x0E,
    0xEE,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xE0,
    0xFC,
    0xEE,
    0xEE,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFE,
    0xEE,
    0x1C,
    0x1C,
    0x38,
    0x38,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xEE,
    0x7C,
    0xEE,
    0xEE,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xEE,
    0xEE,
    0x7E,
    0x0E,
    0x3C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x60,
    0x60,
    0x00,
    0x00,
    0x60,
    0x60,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x60,
    0x60,
    0x00,
    0x00,
    0x60,
    0x60,
    0xC0,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x1C,
    0x38,
    0x70,
    0x70,
    0x38,
    0x1C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0x00,
    0x00,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x70,
    0x38,
    0x1C,
    0x1C,
    0x38,
    0x70,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xEE,
    0x1C,
    0x38,
    0x00,
    0x38,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xEE,
    0xEE,
    0xEE,
    0xE0,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xEE,
    0xEE,
    0xEE,
    0xFE,
    0xEE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFC,
    0xEE,
    0xFC,
    0xEE,
    0xEE,
    0xFC,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xEE,
    0xE0,
    0xE0,
    0xEE,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xF8,
    0xEC,
    0xEE,
    0xEE,
    0xEE,
    0xFC,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFE,
    0xE0,
    0xF0,
    0xE0,
    0xE0,
    0xFE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFE,
    0xE0,
    0xF8,
    0xE0,
    0xE0,
    0xE0,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xE0,
    0xEE,
    0xEE,
    0xEE,
    0x7E,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xEE,
    0xEE,
    0xFE,
    0xEE,
    0xEE,
    0xEE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0x38,
    0x38,
    0x38,
    0x38,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x0E,
    0x0E,
    0x0E,
    0x0E,
    0xEE,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xEE,
    0xFC,
    0xF8,
    0xEC,
    0xEE,
    0xEE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xE0,
    0xE0,
    0xE0,
    0xE0,
    0xEE,
    0xFE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xC6,
    0xEE,
    0xFE,
    0xFE,
    0xEE,
    0xEE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xCE,
    0xEE,
    0xFE,
    0xFE,
    0xEE,
    0xE6,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xEE,
    0xEE,
    0xEE,
    0xEE,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFC,
    0xEE,
    0xEE,
    0xEE,
    0xFC,
    0xE0,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xEE,
    0xEE,
    0xEE,
    0xEC,
    0x7E,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFC,
    0xEE,
    0xEE,
    0xEE,
    0xFC,
    0xEE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x7C,
    0xE0,
    0x7C,
    0x0E,
    0xEE,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFE,
    0x38,
    0x38,
    0x38,
    0x38,
    0x38,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xEE,
    0xEE,
    0xEE,
    0xEE,
    0xEE,
    0x7C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xEE,
    0xEE,
    0xEE,
    0x6C,
    0x38,
    0x10,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xEE,
    0xEE,
    0xFE,
    0xFE,
    0xEE,
    0xC6,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xEE,
    0x7C,
    0x38,
    0x7C,
    0xEE,
    0xEE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xEE,
    0xEE,
    0xEE,
    0x7C,
    0x38,
    0x38,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0xFE,
    0x1C,
    0x38,
    0x70,
    0xE0,
    0xFE,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    /*{w:16,h:16,bpp:1,count:16,brev:1,np:2,pofs:8,remap:[5,0,1,2,4,6,7,8,9,10,11,12]}*/
    // formation enemy (64,66)
    0x0F,
    0x13,
    0x03,
    0x03,
    0x01,
    0x01,
    0x00,
    0x00,
    0x0C,
    0x11,
    0x04,
    0x08,
    0x12,
    0x04,
    0x08,
    0x08,
    0x0C,
    0x13,
    0x03,
    0x03,
    0x03,
    0x01,
    0x01,
    0x00,
    0x0C,
    0x10,
    0x01,
    0x0C,
    0x30,
    0x02,
    0x04,
    0x18,
    0xF8,
    0xE4,
    0xE0,
    0xE0,
    0xC0,
    0xC0,
    0x80,
    0x00,
    0x18,
    0x44,
    0x10,
    0x08,
    0x24,
    0x10,
    0x08,
    0x08,
    0x18,
    0x64,
    0xE0,
    0xE0,
    0xE0,
    0xC0,
    0xC0,
    0x80,
    0x18,
    0x04,
    0x40,
    0x18,
    0x06,
    0x20,
    0x10,
    0x0C,
    // attackers (68)
    0x00,
    0x00,
    0x04,
    0x08,
    0x04,
    0x03,
    0x03,
    0x0F,
    0x00,
    0x00,
    0x04,
    0x08,
    0x04,
    0x00,
    0x01,
    0x00,
    0x03,
    0x01,
    0x01,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x30,
    0x02,
    0x04,
    0x18,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x10,
    0x08,
    0x10,
    0x60,
    0xE0,
    0xE0,
    0x00,
    0x00,
    0x10,
    0x08,
    0x10,
    0x00,
    0x40,
    0x18,
    0xE0,
    0xC0,
    0xC0,
    0x80,
    0x00,
    0x00,
    0x00,
    0x00,
    0x06,
    0x20,
    0x10,
    0x0C,
    0x00,
    0x00,
    0x00,
    0x00,

    0x00,
    0x00,
    0x02,
    0x04,
    0x04,
    0x03,
    0x03,
    0x03,
    0x00,
    0x00,
    0x02,
    0x04,
    0x04,
    0x20,
    0x11,
    0x0C,
    0x03,
    0x07,
    0x03,
    0x03,
    0x02,
    0x00,
    0x00,
    0x00,
    0x00,
    0x20,
    0x1C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x08,
    0x08,
    0x10,
    0xE0,
    0xE0,
    0x00,
    0x00,
    0x00,
    0x08,
    0x08,
    0x10,
    0x00,
    0x40,
    0xE0,
    0xC0,
    0xC0,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x10,
    0x0C,
    0x02,
    0x20,
    0x10,
    0x0C,
    0x00,
    0x00,

    0x00,
    0x00,
    0x02,
    0x04,
    0x04,
    0x03,
    0x03,
    0x03,
    0x00,
    0x00,
    0x02,
    0x04,
    0x04,
    0x20,
    0x11,
    0x0C,
    0x03,
    0x07,
    0x03,
    0x03,
    0x02,
    0x00,
    0x00,
    0x00,
    0x00,
    0x20,
    0x1C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x08,
    0x08,
    0x10,
    0xE0,
    0xE0,
    0x00,
    0x00,
    0x00,
    0x08,
    0x08,
    0x10,
    0x00,
    0x40,
    0xE0,
    0xC0,
    0xC0,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x10,
    0x0C,
    0x02,
    0x20,
    0x10,
    0x0C,
    0x00,
    0x00,

    0x00,
    0x00,
    0x01,
    0x01,
    0x01,
    0x01,
    0x03,
    0x03,
    0x00,
    0x00,
    0x01,
    0x11,
    0x08,
    0x04,
    0x01,
    0x20,
    0x07,
    0x07,
    0x03,
    0x02,
    0x00,
    0x00,
    0x00,
    0x00,
    0x10,
    0x08,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x80,
    0x00,
    0x04,
    0xCC,
    0xF0,
    0xE0,
    0x00,
    0x00,
    0x80,
    0x00,
    0x04,
    0x0C,
    0x00,
    0x40,
    0xC0,
    0xC0,
    0x80,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x20,
    0x10,
    0x08,
    0x80,
    0x40,
    0x20,
    0x00,
    0x00,

    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x01,
    0x00,
    0x00,
    0x00,
    0x04,
    0x02,
    0x11,
    0x08,
    0x04,
    0x03,
    0x03,
    0x07,
    0x01,
    0x00,
    0x00,
    0x00,
    0x00,
    0x04,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x30,
    0x20,
    0x60,
    0xF0,
    0xFC,
    0x00,
    0x00,
    0x00,
    0x30,
    0x20,
    0x00,
    0x40,
    0x04,
    0xFC,
    0xE0,
    0xC0,
    0x80,
    0x00,
    0x00,
    0x00,
    0x00,
    0x2C,
    0x00,
    0x20,
    0x18,
    0x80,
    0x40,
    0x60,
    0x00,

    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x03,
    0x07,
    0x00,
    0x00,
    0x00,
    0x10,
    0x10,
    0x08,
    0x04,
    0x00,
    0x07,
    0x03,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x02,
    0x02,
    0x02,
    0x02,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x08,
    0x34,
    0xE0,
    0xE0,
    0xF0,
    0x00,
    0x80,
    0x80,
    0x88,
    0x94,
    0x00,
    0x40,
    0x00,
    0xFA,
    0xE4,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x2A,
    0x04,
    0x80,
    0x40,
    0x20,
    0x00,
    0x00,
    0x00,

    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x01,
    0x07,
    0x00,
    0x00,
    0x01,
    0x09,
    0x08,
    0x04,
    0x02,
    0x00,
    0x0F,
    0x07,
    0x01,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x02,
    0x04,
    0x08,
    0x09,
    0x01,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x08,
    0x14,
    0xE0,
    0xE0,
    0x00,
    0x00,
    0x00,
    0x00,
    0x88,
    0x94,
    0x00,
    0x40,
    0xC0,
    0xE0,
    0xE0,
    0x14,
    0x08,
    0x00,
    0x00,
    0x00,
    0x00,
    0x40,
    0x00,
    0x94,
    0x88,
    0x00,
    0x00,
    0x00,

    0x01,
    0x01,
    0x01,
    0x00,
    0x00,
    0x01,
    0x11,
    0x11,
    0x01,
    0x01,
    0x02,
    0x07,
    0x01,
    0x21,
    0x21,
    0x2B,
    0x11,
    0x16,
    0x1E,
    0x15,
    0x10,
    0x10,
    0x10,
    0x10,
    0x3F,
    0x3F,
    0x3F,
    0x3D,
    0x3B,
    0x3C,
    0x28,
    0x28,
    0x40,
    0x40,
    0xC0,
    0x00,
    0x00,
    0x40,
    0x44,
    0x44,
    0xC0,
    0xC0,
    0x20,
    0xF0,
    0xC0,
    0xC2,
    0xC2,
    0xEA,
    0x44,
    0x34,
    0x3C,
    0x54,
    0x04,
    0x04,
    0x04,
    0x04,
    0xFE,
    0xFE,
    0xFE,
    0xDE,
    0xEE,
    0x1E,
    0x0A,
    0x0A,
    // player ship (96)
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x18,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x08,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    // bullet (100)
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x04,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    // stars (102)
    0x00,
    0x00,
    0x00,
    0x08,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x08,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x10,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    // explosions (112)
    0x00,
    0x00,
    0x00,
    0x08,
    0x04,
    0x03,
    0x06,
    0x05,
    0x00,
    0x00,
    0x00,
    0x0C,
    0x06,
    0x04,
    0x00,
    0x18,
    0x05,
    0x06,
    0x07,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x18,
    0x00,
    0x08,
    0x09,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x08,
    0x90,
    0xE0,
    0x90,
    0xD0,
    0x00,
    0x00,
    0x00,
    0x18,
    0x30,
    0x10,
    0x00,
    0x0C,
    0xF0,
    0x20,
    0xE0,
    0x80,
    0x00,
    0x00,
    0x00,
    0x00,
    0x0C,
    0x10,
    0x18,
    0xC8,
    0x00,
    0x00,
    0x00,
    0x00,

    0x00,
    0x04,
    0x12,
    0x0C,
    0x42,
    0x30,
    0x00,
    0x19,
    0x00,
    0x00,
    0x00,
    0x0A,
    0x00,
    0x06,
    0x08,
    0x00,
    0x11,
    0x04,
    0x0C,
    0x00,
    0x21,
    0x46,
    0x00,
    0x00,
    0x04,
    0x10,
    0x02,
    0x0C,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x40,
    0x08,
    0x91,
    0xA2,
    0x04,
    0x80,
    0xCC,
    0x00,
    0x00,
    0x00,
    0x20,
    0x00,
    0x30,
    0x08,
    0x00,
    0xC4,
    0x10,
    0x1A,
    0x80,
    0x20,
    0x10,
    0x00,
    0x00,
    0x10,
    0x04,
    0xA0,
    0x98,
    0x80,
    0x00,
    0x00,
    0x00,

    0x61,
    0x20,
    0x00,
    0x04,
    0x00,
    0xC4,
    0x08,
    0x10,
    0x00,
    0x00,
    0x04,
    0x06,
    0x09,
    0x02,
    0x10,
    0x14,
    0x10,
    0x00,
    0x42,
    0xC8,
    0x80,
    0x00,
    0x10,
    0x30,
    0x34,
    0x10,
    0x11,
    0x02,
    0x00,
    0x00,
    0x00,
    0x00,
    0x86,
    0xC4,
    0x00,
    0x10,
    0x01,
    0x10,
    0x08,
    0x04,
    0x00,
    0x00,
    0x10,
    0x30,
    0x48,
    0x20,
    0x04,
    0x14,
    0x04,
    0x00,
    0x20,
    0x89,
    0x00,
    0x84,
    0xC6,
    0x43,
    0x16,
    0x04,
    0x44,
    0xA0,
    0x00,
    0x00,
    0x00,
    0x00,

    0x00,
    0x00,
    0x02,
    0x08,
    0x00,
    0x06,
    0x02,
    0x00,
    0x00,
    0x40,
    0x08,
    0x01,
    0x11,
    0x15,
    0x00,
    0x38,
    0x30,
    0x02,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x00,
    0x18,
    0x00,
    0x10,
    0x00,
    0x04,
    0x40,
    0x00,
    0x00,
    0x00,
    0x00,
    0x20,
    0x08,
    0x00,
    0x30,
    0x20,
    0x00,
    0x02,
    0x00,
    0x08,
    0x40,
    0x44,
    0x54,
    0x00,
    0x0E,
    0x06,
    0x20,
    0x80,
    0x80,
    0x00,
    0x00,
    0x00,
    0x00,
    0x0C,
    0x80,
    0x04,
    0x80,
    0x00,
    0x11,
    0x00,
    0x00,
};

#define CHAR(x) ((x) - ' ')
#define BLANK 0

void clrscr()
{
  vrambuf_clear();
  ppu_off();
  vram_adr(NAMETABLE_A);
  vram_fill(BLANK, 32 * 28);
  vram_adr(0x0);
  ppu_on_all();
}

/////

char in_rect(byte x, byte y, byte x0, byte y0, byte w, byte h)
{
  return ((byte)(x - x0) < w && (byte)(y - y0) < h); // unsigned
}

void draw_bcd_word(byte col, byte row, word bcd)
{
  byte j;
  static char buf[5];
  buf[4] = CHAR('0');
  for (j = 3; j < 0x80; j--)
  {
    buf[j] = CHAR('0' + (bcd & 0xf));
    bcd >>= 4;
  }
  vrambuf_put(NTADR_A(col, row), buf, 5);
}

// GAME CODE

#define NSPRITES 8     // max number of sprites
#define NMISSILES 8    // max number of missiles
#define YOFFSCREEN 240 // offscreen y position (hidden)

// sprite indexes
#define PLYRMISSILE 7 // player missile
#define PLYRSPRITE 7  // player sprite
#define BOOMSPRITE 6  // explosion sprite

// nametable entries
#define NAME_SHIP 96
#define NAME_MISSILE 100
#define NAME_BOMB 104
#define NAME_EXPLODE 112
#define NAME_ENEMY 68

#define SSRC_FORM1 64  // name for formation source sprites
#define SDST_FORM1 128 // start of shifted formation table

typedef struct
{
  byte shape;
} FormationEnemy;

// should be power of 2 length
typedef struct
{
  byte findex;
  byte shape;
  word x;
  word y;
  byte dir;
  byte returning;
} AttackingEnemy;

typedef struct
{
  byte xpos;
  byte ypos;
  signed char dx;
  signed char dy;
} Missile;

typedef struct
{
  byte x;
  byte y;
  byte name;
  byte tag;
} Sprite;

#define ENEMIES_PER_ROW 8
#define ENEMY_ROWS 4
#define MAX_IN_FORMATION (ENEMIES_PER_ROW * ENEMY_ROWS)
#define MAX_ATTACKERS 6

FormationEnemy formation[MAX_IN_FORMATION];
AttackingEnemy attackers[MAX_ATTACKERS];
Missile missiles[NMISSILES];
Sprite vsprites[NSPRITES];

byte formation_offset_x;
signed char formation_direction;
byte current_row;
byte player_x;
const byte player_y = 190;
byte player_exploding;
byte enemy_exploding;
byte enemies_left;
word player_score;

void copy_sprites()
{
  byte i;
  byte oamid = 128; // so we don't clear stars...
  for (i = 0; i < NSPRITES; i++)
  {
    Sprite *spr = &vsprites[i];
    if (spr->y != YOFFSCREEN)
    {
      byte y = spr->y;
      byte x = spr->x;
      byte chr = spr->name;
      byte attr = spr->tag;
      if (attr & 0x40)
        chr += 2; // horiz flip, swap tiles
      oamid = oam_spr(x, y, chr, attr, oamid);
      oamid = oam_spr(x + 8, y, chr ^ 2, attr, oamid);
    }
  }
  // copy all "shadow missiles" to video memory
  for (i = 0; i < NMISSILES; i++)
  {
    Missile *mis = &missiles[i];
    if (mis->ypos != YOFFSCREEN)
    {
      oamid = oam_spr(mis->xpos, mis->ypos, NAME_MISSILE,
                      (i == 7) ? COLOR_MISSILE : COLOR_BOMB,
                      oamid);
    }
  }
  oam_hide_rest(oamid);
}

void add_score(word bcd)
{
  player_score = bcd_add(player_score, bcd);
  draw_bcd_word(1, 1, player_score);
}

void clrobjs()
{
  byte i;
  memset(vsprites, 0, sizeof(vsprites));
  for (i = 0; i < NSPRITES; i++)
  {
    vsprites[i].y = YOFFSCREEN;
  }
  for (i = 0; i < NMISSILES; i++)
  {
    missiles[i].ypos = YOFFSCREEN;
  }
}

void setup_formation()
{
  byte i;
  memset(formation, 0, sizeof(formation));
  memset(attackers, 0, sizeof(attackers));
  for (i = 0; i < MAX_IN_FORMATION; i++)
  {
    byte flagship = i < ENEMIES_PER_ROW;
    formation[i].shape = flagship ? SDST_FORM1 : SDST_FORM1;
  }
  enemies_left = MAX_IN_FORMATION;
  formation_offset_x = 8;
}

void draw_row(byte row)
{
  static char buf[32];
  register byte i;
  register byte x = formation_offset_x / 8;
  byte xd = (formation_offset_x & 7) * 3;
  byte y = 3 + row * 2;
  memset(buf, BLANK, sizeof(buf));
  for (i = 0; i < ENEMIES_PER_ROW; i++)
  {
    byte shape = formation[i + row * ENEMIES_PER_ROW].shape;
    if (shape)
    {
      shape += xd;
      buf[x] = shape;
      buf[x + 1] = shape + 1;
      buf[x + 2] = shape + 2;
    }
    x += 3;
  }
  vrambuf_put(NTADR_A(0, y), buf, sizeof(buf));
}

void draw_next_row()
{
  draw_row(current_row);
  if (++current_row == ENEMY_ROWS)
  {
    current_row = 0;
    formation_offset_x += formation_direction;
    if (formation_offset_x == 63)
    {
      formation_direction = -1;
    }
    else if (formation_offset_x == 8)
    {
      formation_direction = 1;
    }
  }
}

#define FLIPX 0x40
#define FLIPY 0x80
#define FLIPXY 0xc0

const byte DIR_TO_CODE[32] = {
    0 | FLIPXY,
    1 | FLIPXY,
    2 | FLIPXY,
    3 | FLIPXY,
    4 | FLIPXY,
    5 | FLIPXY,
    6 | FLIPXY,
    6 | FLIPXY,
    6 | FLIPX,
    6 | FLIPX,
    5 | FLIPX,
    4 | FLIPX,
    3 | FLIPX,
    2 | FLIPX,
    1 | FLIPX,
    0 | FLIPX,
    0,
    1,
    2,
    3,
    4,
    5,
    6,
    6,
    6 | FLIPY,
    6 | FLIPY,
    5 | FLIPY,
    4 | FLIPY,
    3 | FLIPY,
    2 | FLIPY,
    1 | FLIPY,
    0 | FLIPY,
};

// sine table, pre-multiplied by 2
const int SINTBL2[32] = {
    0,
    25 * 2,
    49 * 2,
    71 * 2,
    90 * 2,
    106 * 2,
    117 * 2,
    125 * 2,
    127 * 2,
    125 * 2,
    117 * 2,
    106 * 2,
    90 * 2,
    71 * 2,
    49 * 2,
    25 * 2,
    0 * 2,
    -25 * 2,
    -49 * 2,
    -71 * 2,
    -90 * 2,
    -106 * 2,
    -117 * 2,
    -125 * 2,
    -127 * 2,
    -125 * 2,
    -117 * 2,
    -106 * 2,
    -90 * 2,
    -71 * 2,
    -49 * 2,
    -25 * 2,
};

#define ISIN(x) (SINTBL2[(x)&31])
#define ICOS(x) ISIN(x + 8)

#define FORMATION_X0 0
#define FORMATION_Y0 19
#define FORMATION_XSPACE 24
#define FORMATION_YSPACE 16

byte get_attacker_x(byte formation_index)
{
  byte column = (formation_index % ENEMIES_PER_ROW);
  return FORMATION_XSPACE * column + FORMATION_X0 + formation_offset_x;
}

byte get_attacker_y(byte formation_index)
{
  byte row = formation_index / ENEMIES_PER_ROW;
  return FORMATION_YSPACE * row + FORMATION_Y0;
}

void draw_attacker(byte i)
{
  AttackingEnemy *a = &attackers[i];
  if (a->findex)
  {
    byte code = DIR_TO_CODE[a->dir & 31];
    vsprites[i].name = NAME_ENEMY + (code & 7) * 4; // tile
    vsprites[i].tag = code & FLIPXY;                // flip h/v
    vsprites[i].x = a->x >> 8;
    vsprites[i].y = a->y >> 8;
  }
  else
  {
    vsprites[i].y = YOFFSCREEN;
  }
}

void draw_attackers()
{
  byte i;
  for (i = 0; i < MAX_ATTACKERS; i++)
  {
    draw_attacker(i);
  }
}

void return_attacker(register AttackingEnemy *a)
{
  byte fi = a->findex - 1;
  byte destx = get_attacker_x(fi);
  byte desty = get_attacker_y(fi);
  byte ydist = desty - (a->y >> 8);
  // are we close to our formation slot?
  if (ydist == 0)
  {
    // convert back to formation enemy
    formation[fi].shape = a->shape;
    a->findex = 0;
  }
  else
  {
    a->dir = (ydist + 16) & 31;
    a->x = destx << 8;
    a->y += 128;
  }
}

void fly_attacker(register AttackingEnemy *a)
{
  a->x += ISIN(a->dir);
  a->y += ICOS(a->dir);
  if ((a->y >> 8) == 0)
  {
    a->returning = 1;
  }
}

void move_attackers()
{
  byte i;
  for (i = 0; i < MAX_ATTACKERS; i++)
  {
    AttackingEnemy *a = &attackers[i];
    if (a->findex)
    {
      if (a->returning)
        return_attacker(a);
      else
        fly_attacker(a);
    }
  }
}

void think_attackers()
{
  byte i;
  for (i = 0; i < MAX_ATTACKERS; i++)
  {
    AttackingEnemy *a = &attackers[i];
    if (a->findex)
    {
      // rotate?
      byte x = a->x >> 8;
      byte y = a->y >> 8;
      // don't shoot missiles after player exploded
      if (y < 112 || player_exploding)
      {
        if (x < 128)
        {
          a->dir++;
        }
        else
        {
          a->dir--;
        }
      }
      else
      {
        // lower half of screen
        // shoot a missile?
        if (missiles[i].ypos == YOFFSCREEN)
        {
          missiles[i].ypos = y + 16;
          missiles[i].xpos = x;
          missiles[i].dy = 2;
        }
      }
    }
  }
}

void formation_to_attacker(byte formation_index)
{
  byte i;
  // out of bounds? return
  if (formation_index >= MAX_IN_FORMATION)
    return;
  // nobody in formation? return
  if (!formation[formation_index].shape)
    return;
  // find an empty attacker slot
  for (i = 0; i < MAX_ATTACKERS; i++)
  {
    AttackingEnemy *a = &attackers[i];
    if (a->findex == 0)
    {
      a->x = get_attacker_x(formation_index) << 8;
      a->y = get_attacker_y(formation_index) << 8;
      a->shape = formation[formation_index].shape;
      a->findex = formation_index + 1;
      a->dir = 0;
      a->returning = 0;
      formation[formation_index].shape = 0;
      break;
    }
  }
}

void draw_player()
{
  vsprites[PLYRSPRITE].x = player_x;
  vsprites[PLYRSPRITE].y = player_y;
  vsprites[PLYRSPRITE].name = NAME_SHIP;
  vsprites[PLYRSPRITE].tag = COLOR_PLAYER;
}

void move_player()
{
  byte joy = pad_poll(0);
  // move left/right?
  if ((joy & PAD_LEFT) && player_x > 16)
    player_x--;
  if ((joy & PAD_RIGHT) && player_x < 224)
    player_x++;
  // shoot missile?
  if ((joy & PAD_A) && missiles[PLYRMISSILE].ypos == YOFFSCREEN)
  {
    missiles[PLYRMISSILE].ypos = player_y - 8; // must be multiple of missile speed
    missiles[PLYRMISSILE].xpos = player_x + 4; // player X position
    missiles[PLYRMISSILE].dy = -4;             // player missile speed
  }
  vsprites[PLYRMISSILE].x = player_x;
}

void move_missiles()
{
  byte i;
  for (i = 0; i < 8; i++)
  {
    if (missiles[i].ypos != YOFFSCREEN)
    {
      // hit the bottom or top?
      if ((byte)(missiles[i].ypos += missiles[i].dy) > YOFFSCREEN)
      {
        missiles[i].ypos = YOFFSCREEN;
      }
    }
  }
}

void blowup_at(byte x, byte y)
{
  vsprites[BOOMSPRITE].tag = COLOR_EXPLOSION;
  vsprites[BOOMSPRITE].name = NAME_EXPLODE; // TODO
  vsprites[BOOMSPRITE].x = x;
  vsprites[BOOMSPRITE].y = y;
  enemy_exploding = 1;
}

void animate_enemy_explosion()
{
  if (enemy_exploding)
  {
    // animate next frame
    if (enemy_exploding >= 8)
    {
      enemy_exploding = 0; // hide explosion after 4 frames
      vsprites[BOOMSPRITE].y = YOFFSCREEN;
    }
    else
    {
      vsprites[BOOMSPRITE].name = NAME_EXPLODE + (enemy_exploding += 4); // TODO
    }
  }
}

void animate_player_explosion()
{
  byte z = player_exploding;
  if (z <= 3)
  {
    if (z == 3)
    {
      vsprites[PLYRSPRITE].y = YOFFSCREEN;
    }
    else
    {
      vsprites[PLYRSPRITE].name = NAME_EXPLODE + z * 4;
    }
  }
}

void hide_player_missile()
{
  missiles[PLYRMISSILE].ypos = YOFFSCREEN;
}

void does_player_shoot_formation()
{
  byte mx = missiles[PLYRMISSILE].xpos + 4;
  byte my = missiles[PLYRMISSILE].ypos;
  signed char row = (my - FORMATION_Y0) / FORMATION_YSPACE;
  if (missiles[PLYRMISSILE].ypos == YOFFSCREEN)
    return;
  if (row >= 0 && row < ENEMY_ROWS)
  {
    // ok if unsigned (in fact, must be due to range)
    byte xoffset = mx - FORMATION_X0 - formation_offset_x;
    byte column = xoffset / FORMATION_XSPACE;
    byte localx = xoffset - column * FORMATION_XSPACE;
    if (column < ENEMIES_PER_ROW && localx < 16)
    {
      char index = column + row * ENEMIES_PER_ROW;
      if (formation[index].shape)
      {
        formation[index].shape = 0;
        enemies_left--;
        blowup_at(get_attacker_x(index), get_attacker_y(index));
        hide_player_missile();
        add_score(2);
      }
    }
  }
}

void does_player_shoot_attacker()
{
  byte mx = missiles[PLYRMISSILE].xpos + 4;
  byte my = missiles[PLYRMISSILE].ypos;
  byte i;
  if (missiles[PLYRMISSILE].ypos == YOFFSCREEN)
    return;
  for (i = 0; i < MAX_ATTACKERS; i++)
  {
    AttackingEnemy *a = &attackers[i];
    if (a->findex && in_rect(mx, my, a->x >> 8, a->y >> 8, 16, 16))
    {
      blowup_at(a->x >> 8, a->y >> 8);
      a->findex = 0;
      enemies_left--;
      hide_player_missile();
      add_score(5);
      break;
    }
  }
}

void does_missile_hit_player()
{
  byte i;
  if (player_exploding)
    return;
  for (i = 0; i < MAX_ATTACKERS; i++)
  {
    if (missiles[i].ypos != YOFFSCREEN &&
        in_rect(missiles[i].xpos, missiles[i].ypos + 16,
                player_x, player_y, 16, 16))
    {
      player_exploding = 1;
      break;
    }
  }
}

void new_attack_wave()
{
  byte i = rand();
  byte j;
  // find a random slot that has an enemy
  for (j = 0; j < MAX_IN_FORMATION; j++)
  {
    i = (i + 1) & (MAX_IN_FORMATION - 1);
    // anyone there?
    if (formation[i].shape)
    {
      formation_to_attacker(i);
      formation_to_attacker(i + 1);
      formation_to_attacker(i + ENEMIES_PER_ROW);
      formation_to_attacker(i + ENEMIES_PER_ROW + 1);
      break;
    }
  }
}

void new_player_ship()
{
  player_exploding = 0;
  player_x = 120;
  draw_player();
}

void set_sounds()
{
  byte i;
  // these channels decay, so ok to always enable
  byte enable = ENABLE_PULSE0 | ENABLE_PULSE1 | ENABLE_NOISE;
  // missile fire sound
  if (missiles[PLYRMISSILE].ypos != YOFFSCREEN)
  {
    APU_PULSE_SUSTAIN(0, 255 - missiles[PLYRMISSILE].ypos, DUTY_50, 6);
  }
  else
  {
    APU_PULSE_SET_VOLUME(0, DUTY_50, 0);
  }
  // enemy explosion sound
  if (player_exploding && player_exploding < 8)
  {
    APU_NOISE_DECAY(8 + player_exploding, 5, 15);
  }
  else if (enemy_exploding)
  {
    APU_NOISE_DECAY(8 + enemy_exploding, 2, 8);
  }
  // set diving sounds for spaceships
  for (i = 0; i < 2; i++)
  {
    register AttackingEnemy *a = i ? &attackers[4] : &attackers[0];
    if (a->findex && !a->returning)
    {
      byte y = a->y >> 8;
      APU_TRIANGLE_SUSTAIN(0x100 | y);
      enable |= ENABLE_TRIANGLE;
      break;
    }
  }
  APU_ENABLE(enable);
}

void init_stars()
{
  byte oamid = 0; // 32 slots = 128 bytes
  byte i;
  for (i = 0; i < 32; i++)
  {
    oamid = oam_spr(rand(), i * 8, 103 + (i & 3), 0 | OAM_BEHIND, oamid);
  }
}
/*
void draw_stars_c() {
  byte i;
  for (i=0; i<32; i++) {
    ++OAMBUF[i].y;
  }
}
*/
void draw_stars()
{
  asm("ldy #0");         // start with Y = 0
  asm("clc");            // clear carry for addition
  asm("@1: lda $200,y"); // read from OAM buffer
  asm("adc #1");         // increment
  asm("sta $200,y");     // write to OAM buffer
  asm("iny");            // increment Y by 4
  asm("iny");
  asm("iny");
  asm("iny");
  asm("bpl @1"); // branch while < 128
}

void play_round()
{
  register byte framecount;
  register byte t0;
  byte end_timer = 255;
  add_score(0);
  // putbytes(NTADR_A(0, 1), "PLAYER 1", 8);
  setup_formation();
  clrobjs();
  formation_direction = 1;
  framecount = 0;
  new_player_ship();
  while (end_timer)
  {
    if (player_exploding)
    {
      if ((framecount & 7) == 1)
      {
        animate_player_explosion();
        if (++player_exploding > 32 && enemies_left)
        {
          new_player_ship();
        }
      }
    }
    else
    {
      if (framecount == 0 || enemies_left < 8)
      {
        new_attack_wave();
      }
      move_player();
    }
    move_attackers();
    move_missiles();
    if (framecount & 1)
      does_player_shoot_formation();
    else
      does_player_shoot_attacker();
    draw_attackers();
    if ((framecount & 0xf) == 0)
    {
      think_attackers();
    }
    else
    {
      switch (framecount & 3)
      {
      case 1:
        animate_enemy_explosion(); // continue...
      case 2:
        does_missile_hit_player();
        break;
      case 3:
        draw_stars();
        break;
      }
      set_sounds();
      if (!enemies_left)
        end_timer--;
      draw_next_row();
    }
    vrambuf_flush();
    copy_sprites();
#ifdef DEBUG_FRAMERATE
    putchar(t0 & 31, 27, CHAR(' '));
    putchar(framecount & 31, 27, CHAR(' '));
#endif
    framecount++;
    t0 = nesclock();
#ifdef DEBUG_FRAMERATE
    putchar(t0 & 31, 27, CHAR('C'));
    putchar(framecount & 31, 27, CHAR('F'));
#endif
  }
}

// turn off aggressive inlining to save a few bytes
// functions after this point aren't called often
#pragma codesize(100)

void set_shifted_pattern(const byte *src, word dest, byte shift)
{
  static byte buf[16 * 3];
  byte y;
  for (y = 0; y < 16; y++)
  {
    byte a = src[y];
    byte b = src[y + 32];
    buf[y] = a >> shift;
    buf[y + 16] = b >> shift | a << (8 - shift);
    buf[y + 32] = b << (8 - shift);
  }
  vram_adr(dest);
  vram_write(buf, sizeof(buf));
}

void setup_graphics()
{
  byte i;
  word src;
  word dest;
  // copy background
  vram_adr(0x0);
  vram_write(TILESET, sizeof(TILESET));
  // copy sprites
  vram_adr(0x1000);
  vram_write(TILESET, sizeof(TILESET));
  // write shifted versions of formation ships
  src = SSRC_FORM1 * 16;
  dest = SDST_FORM1 * 16;
  for (i = 0; i < 8; i++)
  {
    if (i == 4)
      src += 16;
    set_shifted_pattern(&TILESET[src], dest, i);
    dest += 3 * 16;
  }
  // activate vram buffer
  vrambuf_clear();
  set_vram_update(updbuf);
}

void main()
{
  setup_graphics();
  apu_init();
  player_score = 0;
  while (1)
  {
    pal_all(PALETTE);
    oam_clear();
    oam_size(1); // 8x16 sprites
    clrscr();
    init_stars();
    play_round();
  }
}
